/*
 * Copyright (C) 2010 by Lei Shi (sjtushi@gmail.com)
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License, 
 * version 2, or any later versions.
 * --------------------
 * This xen_monitor daemon used as monitor engine.
 *
 * This service will do the works as listed: 
 *
 *   1. Load profile xml file from ioctl request of control app.
 *   2. Collect data from xen dmesg buffer.
 *   3. Process dmesg data and retrieve process file access info.
 *   4. Compare processed info with profile and log the alarm.
 *   5. Communicate with control app.
 *   6. Handle control app signal to update the monitored domid, 
 *      pid and reload profile.
 *
 */
#include "xenmonitor.h"

int handle_ctrl_cmd(unsigned int cmd, unsigned int param) {

   switch (cmd) { 	
      case CMD_SET_DOMID: 
	 profile.domid = param;
	 break;
      case CMD_SET_PID: 
	 profile.pid = param;
	 break;
      case CMD_RELOAD_PROFILE: 
	 reload_profile();
      default:
	 return STATUS_ERROR;
   }
      
   return STATUS_SUCCESS;
}

int collect_data(char *dmesgbuf,unsigned int count) {
   int xc_handle, ret;

   if ((xc_handle = xc_interface_open()) == STATUS_ERROR) {
      return STATUS_ERROR;
   }

   ret = xc_readconsolering(xc_handle, &dmesgbuf, &count, 1, 0, 0);

   if (ret == -1) {
      return STATUS_ERROR;
   }

   xc_interface_close(xc_handle);

   return STATUS_SUCCESS;

}

void add_to_record_profile(
      int domid, int pid, const char *filename, int access) {
#ifdef DEBUG
   FILE* file = fopen(RECORD_PROFILE_FILE, "a");
   if (file != NULL) {
      fprintf(file, 
	    "MONITOR:%d:%d:%d:%s\n", 
	    domid, pid, access, filename);
   }

   fclose(file);
#else
   /*
    * Create XML format profile for this process.
    *
    */
   xmlDicPtr doc = xmlNewDoc((const xmlChar*)"1.0");
   xmlNodePtr root_node = 
      xmlNewNode(NULL, (const xmlChar*)"profile");
   xmlDocSetRootElement(doc, root_node);

   int ret = xmlSaveFile(RECORD_PROFILE_FILE, doc);
   xmlFreeDoc(doc);


#endif

}


int validate_access(
      int domid, int pid,const char* filepath, int access) {
   int i;
   for (i = 0; i < profile.rule_num; i++) {
      if(!strcmp(profile.rule_list[i].filename, filepath)) {
	 if (access == profile.rule_list[i].type)
	   return  ACCESS_ALLOWED;
	 else
	   return  ACCESS_INVALID;
      }
   }
   
   return ACCESS_INVALID;
}

int process_data(char *dmesgbuf) {

#ifdef _PVGUEST_BOOT_
   char *buf = dmesgbuf;
#else
   char test[]= "(XEN) MONITOR:1:2000:2:/etc/abcd/abc\n \
		 (XEN) MONITOR:2:1234:1:/abcd/etc/aaa/a\n \
		 (XEN) MONITOR:3:1010:0:/aaa/aaa\n \
		 (XEN) abcdefg";
   char *buf = test;
   profile.domid = 2;
   profile.pid = 1234;
#endif


   char *trimstr = NULL;
   char *token1 = strstr(buf, "\n");
   char *token2 = NULL;
   int index = 0;
   int domid, pid, access;
   char* filepath;
   int needvalid = 0;

   while (token1 != NULL) {
      token1[0] = '\0';
      
      if ((trimstr = strstr(buf, "MONITOR")) != NULL) {
	 token2 = strtok(trimstr, ":");
	 index = needvalid = 0;
	 pid = domid = access = -1;
	 filepath = NULL;
	 while (token2 != NULL) {
	    if (index == 1)
	       domid = atoi(token2);
	    else if (index == 2)
	       pid = atoi(token2);
	    else if (index == 3) {
	       /*
		* Current implementation limit on only 1 monitored domain
		* and 1 monitored process.
		*/
	       if (domid == profile.domid && 
		  pid == profile.pid) {
   		  access = atoi(token2);
   		  needvalid++;
	       } else {
	       	  if (runmode == RUNMODE_MONITOR_MODE)
   		     break;
		  else if (runmode == RUNMODE_RECORD_MODE)
		     access = atoi(token2);
	       }
	    }
	    else if (index == 4)
	       filepath = token2;
	    else if (index != 0)
	       break;
	    index++;
	    token2 = strtok(NULL, ":");
	 }

	 if (needvalid && 
	       runmode == RUNMODE_MONITOR_MODE) {
	       if (validate_access(domid, pid, filepath, access) 
		     != ACCESS_ALLOWED) {
		  char flagstr[20];
		  switch (access) {
	    	     case 0: sprintf(flagstr, "0(read only)"); break;
	    	     case 1: sprintf(flagstr, "1(write only)"); break;
	    	     case 2: sprintf(flagstr, "2(read & write)"); break;
	    	     default: sprintf(flagstr, 
				    "%d(Unknown mode)", access); break;
		  }
		  time_t now;
		  time(&now);
		  DPRINTF("%s ALERT: Access %s with %s mode\n\n", 
			ctime(&now), filepath, flagstr);
#ifndef DEBUG
		  LOGALERT("Access Alert(%s, %d) in Domid %d Pid %d.",
			filepath, access, domid, pid);
#endif
	       }
	 }

	 if (runmode == RUNMODE_RECORD_MODE) {
	    add_to_record_profile(domid, pid, filepath, access);
	 }
      }
      buf = token1 + 1;
      token1 = strstr(buf, "\n");
   }

   return STATUS_SUCCESS;
}

int monitor_loop(int interval) {
   char _str[BUFFER_SIZE], *str = _str;
   unsigned int count = BUFFER_SIZE;

   while(1) {
#ifdef _PVGUEST_BOOT_
      if (collect_data(str, count) != STATUS_SUCCESS) {
   	 DPRINTF("collect_data failed\n");
   	 return STATUS_ERROR;
      }
#endif
      process_data(str);
      sleep(interval);
   }
}

int init_xen_monitor_daemon (void) {
   int pid, i;
   if ((pid = fork()) != 0) 
      exit(0);
   else if (pid < 0)
      exit(1);
  
   setsid();
   if((pid = fork()) != 0)
      exit(0);
   else if (pid < 0)
      exit(1);

   for (i=0; i < NOFILE; i++)
      close(i);
   chdir("/tmp");
   umask(0);

   return STATUS_SUCCESS;
}

void parser_rule(xmlDocPtr doc, xmlNodePtr rule) {
   xmlNodePtr r = rule->xmlChildrenNode;
   int content = 0;
   int index = profile.rule_num;
   xmlChar *file = NULL;
   xmlChar *value = NULL;

   while (r != NULL) {
      if (!xmlStrcmp(r->name, (const xmlChar*)"filepath")) {
	 file = xmlNodeListGetString(
	       doc, r->xmlChildrenNode, 1);
	 strcpy(profile.rule_list[index].filename, (char *)file);
	 free(file);
	 content++;
      }

      if (!xmlStrcmp(
	       r->name, (const xmlChar*)"access")) {
	 value = xmlNodeListGetString(doc, r->xmlChildrenNode, 1);
	 profile.rule_list[index].type = atoi((char*)value);
	 free(value);
	 content++;
      }
      r = r->next;
   }
   if (content == 2) {
      profile.rule_num += 1;
   }
}

void parser_process(xmlDocPtr doc, xmlNodePtr process) {
   xmlNodePtr pro = process->xmlChildrenNode;
   xmlChar* value = NULL;
   int pid = 0;
   while (pro != NULL) {
      if (!xmlStrcmp(pro->name, (const xmlChar*)"cmd")) {
	 value = xmlNodeListGetString(
	       doc, pro->xmlChildrenNode, 1);
//	 do {
   	    pid = getpid_from_cmd( profile.domid, (const char *)value);
//	 } while(pid > 0);
  	 if (pid > 0) { 
   	    profile.pid = pid;
	 }
	 free(value);
      }

      if (!xmlStrcmp(
	       pro->name, (const xmlChar*)"rule")) {
	 parser_rule(doc, pro);
      }

      pro = pro->next;
   }
}


void parser_domain(xmlDocPtr doc, xmlNodePtr domain) {
   xmlNodePtr dom = domain->xmlChildrenNode;
   xmlChar* value = NULL;
   while (dom != NULL) {
      if (!xmlStrcmp(dom->name, (const xmlChar*)"domid")) {
	 value = xmlNodeListGetString(
	       doc, dom->xmlChildrenNode, 1);
	 profile.domid = atoi((char *)value);
	 free(value);
      }
      if (!xmlStrcmp(
	       dom->name, (const xmlChar*)"process")) {
	 parser_process(doc, dom);
      }
      dom = dom->next;
   }
}

void init_profile() {
   memset(&profile, 0, sizeof(monitor_profile_t));
}

int load_profile(void) {
   xmlDocPtr doc;
   xmlNodePtr cur;
   doc = xmlParseFile(PROFILE_FILE_NAME);
   init_profile();

   if (doc == NULL) {
      DPRINTF("xml parser file failed.\n");
      return STATUS_ERROR;
   }

   cur = xmlDocGetRootElement(doc);
   if (cur == NULL) {
      DPRINTF("xml doc get root failed.\n");
      xmlFreeDoc(doc);
      return STATUS_ERROR;
   }

   if (xmlStrcmp(cur->name, (const xmlChar*)"profile")) {
      DPRINTF("xml doc wrong type, not monitor profile xml.\n");
      xmlFreeDoc(doc);
      return STATUS_ERROR;
   }

   cur = cur->xmlChildrenNode;
   while (cur != NULL) {
      if (!xmlStrcmp(cur->name, (const xmlChar*)"domain")) {
	 parser_domain(doc, cur);
      }
      cur = cur->next;
   }

   xmlFreeDoc(doc);
   return STATUS_SUCCESS;
}

int reload_profile(void) {
   return load_profile();
}

int main() {

#ifndef DEBUG
   init_xen_monitor_daemon();
#endif

   if (load_profile() != STATUS_SUCCESS) {
      return -1;
   }

   if (monitor_loop(1) != STATUS_SUCCESS) {
      return -1;
   }

   return 0;
}

